RESULTADOS ELECCIONES GENERALES JULIO DE 2023 EN ESPAÑA- RESULTADOS CONGRESO¶

GEOSPATIAL DATA SCIENCE - BIG DATA GEOESPACIAL - TIG¶

Marcos Díaz y Marco Fossoul¶

Resultados electorales a escala municipal

Fuente: https://resultados.generales23j.es/es/inicio/0¶
In [1]:
# liberías habituales
%matplotlib inline
import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt
import scipy as scipy
from scipy import stats

# librerías especializadas en análisis geoespacial
import pysal as ps
import geopandas as gpd
import cartopy
import cartopy.crs as ccrs
import skgstat as skg
import seaborn as sns
import mapclassify  

# ignorar warnings
from numba.core.errors import NumbaDeprecationWarning, NumbaPendingDeprecationWarning
import warnings
warnings.simplefilter('ignore', category=NumbaDeprecationWarning)
warnings.simplefilter('ignore', category=FutureWarning) 

Cargamos los datos y creamos un dataframe. A su vez, limpiamos los datos para trabajar más cómodamente.

In [2]:
j23 = gpd.read_file('Elecciones.csv')

# Lista de partidos (vamos a cambiarles el tipo de dato)
columnas_partidos = ['PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS', 'EH Bildu', 'EAJ-PNV', 'B.N.G.',
                     'CCa', 'U.P.N.', 'PACMA', 'CUP-PR', 'FO', 'NC-bc', 'PDeCAT-E-CiU', 'RECORTES CERO',
                     'PUM+J', 'U.P.L.', 'EXISTE', 'PCTE', 'GBAI', 'SY', 'ADELANTE ANDALUCÍA', 'ESCAÑOS EN BLANCO',
                     'JM+', 'XAV', 'BQEX', 'CJ', 'FE de las JONS', 'PAR', 'ESPAÑA VACIADA', 'PH', 'ASTURIAS EXISTE EV',
                     'XH', 'VP', 'Zsi', 'VB', 'POR MI REGIÓN', 'AHORA CANARIAS-PCPC', 'PARTIDO AUTÓNOMOS', 'EVB',
                     'CpM', 'JxG', 'EV-PCAS-TC', 'PREPAL', 'Somos Cc', 'ALM', 'F.I.A.', '3e', 'Ud.Ca', 'GITV',
                     'PUEDE', 'EVC', 'LB', 'UNIDOS SI', '+RDS+', 'CCD', 'FUERZA CÍVICA']

# Iteramos sobre las columnas y convertimos a numérico
for columna in columnas_partidos:
    j23[columna] = j23[columna].str.replace('.', '').astype(int)

# Visualizamos el DataFrame actualizado
#print(j23.head())

j23['Código de Municipio'] = j23['Código de Municipio'].astype(str)
j23['Código de Municipio'] = j23['Código de Municipio'].str.zfill(3)
j23['Código de Provincia'] = j23['Código de Provincia'].astype(str)
j23['Código de Provincia'] = j23['Código de Provincia'].str.zfill(2)
In [3]:
#j23.info()
In [4]:
# Trabajaremos sólo con datos en la Península
j23 = j23[~j23['Nombre de Provincia'].str.contains('Las Palmas|Santa Cruz de Tenerife|Ceuta|Illes Balears|Melilla')]
j23
Out[4]:
Nombre de Comunidad Código de Provincia Nombre de Provincia Código de Municipio Nombre de Municipio Población Número de mesas Total censo electoral Total votantes Votos válidos ... Ud.Ca GITV PUEDE EVC LB UNIDOS SI +RDS+ CCD FUERZA CÍVICA geometry
0 Andalucía 04 Almería 001 Abla 1.247 2 995 747 741 ... 0 0 0 0 0 0 0 0 0 None
1 Andalucía 04 Almería 002 Abrucena 1.221 2 1.034 787 781 ... 0 0 0 0 0 0 0 0 0 None
2 Andalucía 04 Almería 003 Adra 25.300 29 17.557 11.748 11.623 ... 0 0 0 0 5 0 0 0 0 None
3 Andalucía 04 Almería 004 Albanchez 735 1 387 293 291 ... 0 0 0 0 0 0 0 0 0 None
4 Andalucía 04 Almería 005 Alboloduy 615 1 504 397 397 ... 0 0 0 0 0 0 0 0 0 None
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8124 Comunitat Valenciana 46 Valencia / València 262 La Yesa 227 1 206 171 169 ... 0 0 0 0 0 0 0 0 0 None
8125 Comunitat Valenciana 46 Valencia / València 263 Zarra 370 1 250 194 189 ... 0 0 0 0 0 0 0 0 0 None
8126 Comunitat Valenciana 46 Valencia / València 902 Gátova 412 1 373 318 310 ... 0 0 0 0 0 0 0 0 0 None
8127 Comunitat Valenciana 46 Valencia / València 903 San Antonio de Benagéber 9.874 10 6.934 5.454 5.414 ... 0 0 0 0 0 0 0 0 0 None
8128 Comunitat Valenciana 46 Valencia / València 904 Benicull de Xúquer 1.102 1 894 688 679 ... 0 0 0 0 0 0 0 0 0 None

7974 rows × 73 columns

In [5]:
# Convertimos la columna 'Total votantes' a tipo numérico
j23['Total votantes'] = pd.to_numeric(j23['Total votantes'], errors='coerce')

# Agrupamos por provincia y sumamos el total de votantes
suma_votantes_por_provincia = j23.groupby('Nombre de Provincia')['Total votantes'].sum().reset_index()

# Mostramos la tabla resultante
print(suma_votantes_por_provincia)
     Nombre de Provincia  Total votantes
0               A Coruña        8719.730
1               Albacete       24419.836
2     Alicante / Alacant       21848.416
3                Almería       22529.194
4          Araba / Álava       16943.650
5               Asturias       17757.237
6                Badajoz       45266.806
7              Barcelona       39673.243
8                Bizkaia       26755.178
9                 Burgos       53513.376
10             Cantabria       19489.541
11  Castellón / Castelló       29741.673
12           Ciudad Real       27788.413
13                Cuenca       41594.389
14               Cáceres       69200.180
15                 Cádiz        3554.018
16               Córdoba       14484.387
17              Gipuzkoa       17280.705
18                Girona       49878.221
19               Granada       43568.723
20           Guadalajara       27394.707
21                Huelva       13378.041
22                Huesca       42706.561
23                  Jaén       15736.717
24              La Rioja       23829.780
25                  León       57820.138
26                Lleida       54302.999
27                  Lugo       13145.011
28                Madrid       25606.774
29                Murcia        4178.281
30                Málaga       23262.450
31               Navarra       47766.812
32               Ourense       37401.959
33              Palencia       26618.761
34            Pontevedra        2744.118
35             Salamanca       60058.977
36               Segovia       32953.484
37               Sevilla        9584.913
38                 Soria       15008.506
39             Tarragona       37865.901
40                Teruel       31865.297
41                Toledo       43269.546
42   Valencia / València       56473.583
43            Valladolid       36772.655
44                Zamora       49590.388
45              Zaragoza       52132.674
46                 Ávila       34510.790

Cargamos un shapefile con la geometría de los municipios de España. Fuente: https://centrodedescargas.cnig.es/CentroDescargas/index.jsp

In [6]:
muni_shp = gpd.read_file('recintos_municipales_inspire_peninbal_etrs89/recintos_municipales_inspire_peninbal_etrs89.shp')

# Cambiamos el sistema de coordenadas y proyección
muni_shp.crs = {'init' :'epsg:25830'}  
#muni_shp

Al igual que con nuestro dataframe de las elecciones, eliminamos todos los territorios que se encuentren fuera de la península

In [7]:
# Extraer el código del municipio (tres dígitos)
muni_shp['MUNI_CODE'] = muni_shp['NATCODE'].str.extract(r'(\d{3})$')

# Extraer el código de la provincia (dos dígitos)
muni_shp['PROV_CODE'] = muni_shp['NATCODE'].str.extract(r'(\d{2})\d{3}$')

muni_shp = muni_shp[muni_shp['PROV_CODE'].str.contains('07|51|52|35|38|53|54') == False]

# Visualizar el GeoDataFrame actualizado
muni_shp
Out[7]:
INSPIREID COUNTRY NATLEV NATLEVNAME NATCODE NAMEUNIT CODNUT1 CODNUT2 CODNUT3 geometry MUNI_CODE PROV_CODE
0 ES.IGN.BDDAE.34010404001 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34010404001 Abla ES6 ES61 ES611 POLYGON ((-2.785 37.093, -2.784 37.095, -2.784... 001 04
1 ES.IGN.BDDAE.34010404002 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34010404002 Abrucena ES6 ES61 ES611 POLYGON ((-2.890 37.092, -2.890 37.092, -2.889... 002 04
2 ES.IGN.BDDAE.34010404003 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34010404003 Adra ES6 ES61 ES611 POLYGON ((-3.140 36.788, -3.140 36.788, -3.139... 003 04
3 ES.IGN.BDDAE.34010404004 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34010404004 Albanchez ES6 ES61 ES611 POLYGON ((-2.202 37.312, -2.202 37.312, -2.201... 004 04
4 ES.IGN.BDDAE.34010404005 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34010404005 Alboloduy ES6 ES61 ES611 POLYGON ((-2.713 37.078, -2.711 37.080, -2.711... 005 04
... ... ... ... ... ... ... ... ... ... ... ... ...
8116 ES.IGN.BDDAE.34172626179 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34172626179 Viniegra de Arriba ES2 ES23 ES230 POLYGON ((-2.852 42.119, -2.848 42.115, -2.845... 179 26
8117 ES.IGN.BDDAE.34172626178 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34172626178 Viniegra de Abajo ES2 ES23 ES230 POLYGON ((-2.920 42.224, -2.919 42.224, -2.916... 178 26
8118 ES.IGN.BDDAE.34172626180 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34172626180 Zarratón ES2 ES23 ES230 POLYGON ((-2.916 42.528, -2.914 42.528, -2.913... 180 26
8119 ES.IGN.BDDAE.34172626181 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34172626181 Zarzosa ES2 ES23 ES230 POLYGON ((-2.402 42.146, -2.401 42.154, -2.398... 181 26
8120 ES.IGN.BDDAE.34172626183 ES https://inspire.ec.europa.eu/codelist/Administ... Municipio 34172626183 Zorraquín ES2 ES23 ES230 POLYGON ((-3.060 42.338, -3.042 42.337, -3.036... 183 26

7974 rows × 12 columns

Ahora, el número de filas coinciden en ambos, j23 y muni_shp, por lo que podemos fusionar los dos dataframes

UNIMOS "j23" (Elecciones) CON "muni_shp" (Municipios Shape)¶

In [8]:
elecciones = gpd.GeoDataFrame(pd.merge(j23, muni_shp, left_on=['Código de Provincia', 'Código de Municipio'], right_on=['PROV_CODE', 'MUNI_CODE'], how='left'))
#elecciones.head()
In [9]:
# dropeamos columna geometry para limpiar
elecciones = elecciones.drop(columns=['geometry_x'])  # Eliminar la columna 'geometry_x'
elecciones = elecciones.rename(columns={'geometry_y': 'geometry'})  # Renombrar 'geometry_y' a 'geometry'

#sistema de coordenadas y proyección
elecciones.crs = {'init' :'epsg:25830'}  #otra forma de hacerlo es con df.set_geometry('geometry', crs='epsg:25830')

elecciones.info()
<class 'geopandas.geodataframe.GeoDataFrame'>
RangeIndex: 7974 entries, 0 to 7973
Data columns (total 84 columns):
 #   Column                 Non-Null Count  Dtype   
---  ------                 --------------  -----   
 0   Nombre de Comunidad    7974 non-null   object  
 1   Código de Provincia    7974 non-null   object  
 2   Nombre de Provincia    7974 non-null   object  
 3   Código de Municipio    7974 non-null   object  
 4   Nombre de Municipio    7974 non-null   object  
 5   Población              7974 non-null   object  
 6   Número de mesas        7974 non-null   object  
 7   Total censo electoral  7974 non-null   object  
 8   Total votantes         7973 non-null   float64 
 9   Votos válidos          7974 non-null   object  
 10  Votos a candidaturas   7974 non-null   object  
 11  Votos en blanco        7974 non-null   object  
 12  Votos nulos            7974 non-null   object  
 13  PP                     7974 non-null   int64   
 14  PSOE                   7974 non-null   int64   
 15  VOX                    7974 non-null   int64   
 16  SUMAR                  7974 non-null   int64   
 17  ERC                    7974 non-null   int64   
 18  JxCAT - JUNTS          7974 non-null   int64   
 19  EH Bildu               7974 non-null   int64   
 20  EAJ-PNV                7974 non-null   int64   
 21  B.N.G.                 7974 non-null   int64   
 22  CCa                    7974 non-null   int64   
 23  U.P.N.                 7974 non-null   int64   
 24  PACMA                  7974 non-null   int64   
 25  CUP-PR                 7974 non-null   int64   
 26  FO                     7974 non-null   int64   
 27  NC-bc                  7974 non-null   int64   
 28  PDeCAT-E-CiU           7974 non-null   int64   
 29  RECORTES CERO          7974 non-null   int64   
 30  PUM+J                  7974 non-null   int64   
 31  U.P.L.                 7974 non-null   int64   
 32  EXISTE                 7974 non-null   int64   
 33  PCTE                   7974 non-null   int64   
 34  GBAI                   7974 non-null   int64   
 35  SY                     7974 non-null   int64   
 36  ADELANTE ANDALUCÍA     7974 non-null   int64   
 37  ESCAÑOS EN BLANCO      7974 non-null   int64   
 38  JM+                    7974 non-null   int64   
 39  XAV                    7974 non-null   int64   
 40  BQEX                   7974 non-null   int64   
 41  CJ                     7974 non-null   int64   
 42  FE de las JONS         7974 non-null   int64   
 43  PAR                    7974 non-null   int64   
 44  ESPAÑA VACIADA         7974 non-null   int64   
 45  PH                     7974 non-null   int64   
 46  ASTURIAS EXISTE EV     7974 non-null   int64   
 47  XH                     7974 non-null   int64   
 48  VP                     7974 non-null   int64   
 49  Zsi                    7974 non-null   int64   
 50  VB                     7974 non-null   int64   
 51  POR MI REGIÓN          7974 non-null   int64   
 52  AHORA CANARIAS-PCPC    7974 non-null   int64   
 53  PARTIDO AUTÓNOMOS      7974 non-null   int64   
 54  EVB                    7974 non-null   int64   
 55  CpM                    7974 non-null   int64   
 56  JxG                    7974 non-null   int64   
 57  EV-PCAS-TC             7974 non-null   int64   
 58  PREPAL                 7974 non-null   int64   
 59  Somos Cc               7974 non-null   int64   
 60  ALM                    7974 non-null   int64   
 61  F.I.A.                 7974 non-null   int64   
 62  3e                     7974 non-null   int64   
 63  Ud.Ca                  7974 non-null   int64   
 64  GITV                   7974 non-null   int64   
 65  PUEDE                  7974 non-null   int64   
 66  EVC                    7974 non-null   int64   
 67  LB                     7974 non-null   int64   
 68  UNIDOS SI              7974 non-null   int64   
 69  +RDS+                  7974 non-null   int64   
 70  CCD                    7974 non-null   int64   
 71  FUERZA CÍVICA          7974 non-null   int64   
 72  INSPIREID              7974 non-null   object  
 73  COUNTRY                7974 non-null   object  
 74  NATLEV                 7974 non-null   object  
 75  NATLEVNAME             7974 non-null   object  
 76  NATCODE                7974 non-null   object  
 77  NAMEUNIT               7974 non-null   object  
 78  CODNUT1                7974 non-null   object  
 79  CODNUT2                7974 non-null   object  
 80  CODNUT3                7974 non-null   object  
 81  geometry               7974 non-null   geometry
 82  MUNI_CODE              7974 non-null   object  
 83  PROV_CODE              7974 non-null   object  
dtypes: float64(1), geometry(1), int64(59), object(23)
memory usage: 5.1+ MB

Creamos Columna: Partido mas votado

In [10]:
columnas_votos = ['PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS', 'EH Bildu', 'EAJ-PNV', 'B.N.G.', 'CCa', 'U.P.N.', 'PACMA', 'CUP-PR', 'FO', 'NC-bc', 'PDeCAT-E-CiU', 'RECORTES CERO', 'PUM+J', 'U.P.L.', 'EXISTE', 'PCTE', 'GBAI', 'SY', 'ADELANTE ANDALUCÍA', 'ESCAÑOS EN BLANCO', 'JM+', 'XAV', 'BQEX', 'CJ', 'FE de las JONS', 'PAR', 'ESPAÑA VACIADA', 'PH', 'ASTURIAS EXISTE EV', 'XH', 'VP', 'Zsi', 'VB', 'POR MI REGIÓN', 'AHORA CANARIAS-PCPC', 'PARTIDO AUTÓNOMOS', 'EVB', 'CpM', 'JxG', 'EV-PCAS-TC', 'PREPAL', 'Somos Cc', 'ALM', 'F.I.A.', '3e', 'Ud.Ca', 'GITV', 'PUEDE', 'EVC', 'LB', 'UNIDOS SI', '+RDS+', 'CCD', 'FUERZA CÍVICA']

# Convertimos columnas de votos a tipo numérico
elecciones[columnas_votos] = elecciones[columnas_votos].apply(pd.to_numeric, errors='coerce')
In [11]:
# Creamos columna del partido más votado
elecciones['Partido_Mas_Votado'] = elecciones[columnas_votos].idxmax(axis=1)

# Creamos la columna de votos al partido más votado
elecciones['Votos_Partido_Mas_Votado'] = elecciones[columnas_votos].max(axis=1)
elecciones
Out[11]:
Nombre de Comunidad Código de Provincia Nombre de Provincia Código de Municipio Nombre de Municipio Población Número de mesas Total censo electoral Total votantes Votos válidos ... NATCODE NAMEUNIT CODNUT1 CODNUT2 CODNUT3 geometry MUNI_CODE PROV_CODE Partido_Mas_Votado Votos_Partido_Mas_Votado
0 Andalucía 04 Almería 001 Abla 1.247 2 995 747.000 741 ... 34010404001 Abla ES6 ES61 ES611 POLYGON ((-2.785 37.093, -2.784 37.095, -2.784... 001 04 PSOE 294
1 Andalucía 04 Almería 002 Abrucena 1.221 2 1.034 787.000 781 ... 34010404002 Abrucena ES6 ES61 ES611 POLYGON ((-2.890 37.092, -2.890 37.092, -2.889... 002 04 PSOE 343
2 Andalucía 04 Almería 003 Adra 25.300 29 17.557 11.748 11.623 ... 34010404003 Adra ES6 ES61 ES611 POLYGON ((-3.140 36.788, -3.140 36.788, -3.139... 003 04 PP 4985
3 Andalucía 04 Almería 004 Albanchez 735 1 387 293.000 291 ... 34010404004 Albanchez ES6 ES61 ES611 POLYGON ((-2.202 37.312, -2.202 37.312, -2.201... 004 04 PSOE 106
4 Andalucía 04 Almería 005 Alboloduy 615 1 504 397.000 397 ... 34010404005 Alboloduy ES6 ES61 ES611 POLYGON ((-2.713 37.078, -2.711 37.080, -2.711... 005 04 PP 186
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
7969 Comunitat Valenciana 46 Valencia / València 262 La Yesa 227 1 206 171.000 169 ... 34104646262 La Yesa ES5 ES52 ES523 POLYGON ((-0.982 39.840, -0.981 39.847, -0.981... 262 46 PP 68
7970 Comunitat Valenciana 46 Valencia / València 263 Zarra 370 1 250 194.000 189 ... 34104646263 Zarra ES5 ES52 ES523 POLYGON ((-1.214 39.123, -1.211 39.123, -1.209... 263 46 PSOE 89
7971 Comunitat Valenciana 46 Valencia / València 902 Gátova 412 1 373 318.000 310 ... 34104646902 Gátova ES5 ES52 ES523 POLYGON ((-0.581 39.757, -0.577 39.768, -0.577... 902 46 PSOE 121
7972 Comunitat Valenciana 46 Valencia / València 903 San Antonio de Benagéber 9.874 10 6.934 5.454 5.414 ... 34104646903 San Antonio de Benagéber ES5 ES52 ES523 POLYGON ((-0.522 39.576, -0.521 39.576, -0.521... 903 46 PP 2282
7973 Comunitat Valenciana 46 Valencia / València 904 Benicull de Xúquer 1.102 1 894 688.000 679 ... 34104646904 Benicull de Xúquer ES5 ES52 ES523 POLYGON ((-0.405 39.184, -0.405 39.184, -0.404... 904 46 PSOE 247

7974 rows × 86 columns

REPRESENTACIÓN DE LOS DATOS EN UN MAPA INTERACTIVO¶

MAPA DE MARCADORES¶

En primer lugar, vamos a representar un mapa interactivo en el que se muestra un marcador en el centroide de cada municipio de la península. Dicho marcador mostrará información acerca de los resultados de las elecciones de julio de 2023. Por ejemplo, si hacemnos click en el marcador situado en el centroide del municipio de Madrid, nos mostrará la siguiente información: "Municipio: Madrid | Partido más votado: PP | Votos: 720557"

In [12]:
import folium
from folium.plugins import MarkerCluster

# Creamos un mapa centrado en España
m = folium.Map(location=[40.416775, -3.703790], zoom_start=6)

# Creamos marcadores
marker_cluster = MarkerCluster().add_to(m)

# Identificamos los 5 partidos más votados en toda España
top_5_partidos = elecciones.groupby('Partido_Mas_Votado')['Votos_Partido_Mas_Votado'].sum().nlargest(5).index

# Creamos un diccionario de colores para los partidos
colores_partidos = {
    'PP': 'blue',
    'PSOE': 'red',
    'VOX': 'green',
    'SUMAR': 'purple',
    'ERC': 'beige',
    'JxCAT - JUNTS': 'orange',
    'EH Bildu': 'lightgreen',
    'EAJ-PNV': 'pink',
    'B.N.G.': 'lightblue',
    'CCa': 'lightgray',
    'U.P.N.': 'darkred',
}

# leyenda personalizada
legend_html = '<div style="position: fixed; top: 10px; right: 10px; z-index: 1000; background-color: white; padding: 10px; border: 1px solid grey; border-radius: 5px;">'
for partido, color in colores_partidos.items():
    legend_html += f'<p style="margin: 0;"><span style="color: {color}; font-weight: bold;">■</span> {partido}</p>'
legend_html += '</div>'

m.get_root().html.add_child(folium.Element(legend_html))

# Iteramos sobre las filas 
for idx, row in elecciones.iterrows():
    municipio = row['Nombre de Municipio']
    partido_mas_votado = row['Partido_Mas_Votado']
    votos_partido_mas_votado = row['Votos_Partido_Mas_Votado']
    
    # Accedemos a las coordenadas del centroide del polígono
    centroid = row.geometry.centroid
    centroid_lat, centroid_lon = centroid.y, centroid.x
    
    # Creamos un marcador con pop-up
    popup_text = f"Municipio: {municipio}<br>Partido más votado: {partido_mas_votado}<br>Votos: {votos_partido_mas_votado}"
    folium.Marker([centroid_lat, centroid_lon], popup=popup_text, icon=folium.Icon(color=colores_partidos.get(partido_mas_votado, 'gray'))).add_to(marker_cluster)

#mapa
m
Out[12]:
Make this Notebook Trusted to load map: File -> Trust Notebook

MAPA DEL PARTIDO MÁS VOTADO EN CADA MUNICIPIO DE LA PENÍNSULA IBÉRICA¶

A continuación, un mapa simple con la finalidad de representar todos los municipios de la península resaltados con el color que representa el partido político que más votos ha recibido en dicho municipio. Para ello, hemos creado una paleta de colores la cual vamos a continuar utilizando en el resto del proyecto para futuras representaciones. La lista es pequeña puesto que hemos decidido representar a partir de ahora únicamente aquellos partidos políticos que tienen representacion en el Congreso de los Diputados.

In [13]:
# Colores personalizados para los grandes partidos
colores_partidos = {
    'PP': '#42B0EF',
    'PSOE': '#FF4040',
    'VOX': '#52D73E',
    'SUMAR': '#FF3982',
    'ERC': '#F0D733',
    'JxCAT - JUNTS': '#33F0B7',
    'EH Bildu': '#A7E55F',
    'EAJ-PNV': '#AB7D39',
    'U.P.N.': '#164CDF',
}

# Creamos el mapa
fig, ax = plt.subplots(1, 1, figsize=(15, 10))
elecciones.boundary.plot(ax=ax, linewidth=0.7, alpha=0.9)

# Asignaremos los colores a los partidos 
for partido, color in colores_partidos.items():
    elecciones[elecciones['Partido_Mas_Votado'] == partido].plot(color=color, ax=ax)

# Leyenda y título
legend_labels = [plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=color, markersize=10, label=partido) for partido, color in colores_partidos.items()]
ax.legend(handles=legend_labels, title='Partidos Principales', loc='upper left', bbox_to_anchor=(1, 1))
ax.set_title('Partido más votado por municipio')

# Mostrar el mapa
plt.show()
No description has been provided for this image

En blanco, los municipios cuyo partido más votado es uno sin representación en el congreso

MAPA DE CALOR DEL PP¶

Representamos un mapa de calor, en base a los centroides de los municipios, para mostrar la distribución de los votos al Partido Popular en la península.

In [14]:
from folium.plugins import HeatMap

# mapa
m = folium.Map(location=[40.416775, -3.703790], zoom_start=9)

#  mapa de calor utilizando la columna 'PP' 
heat_data_pp = [[row['geometry'].centroid.y, row['geometry'].centroid.x, row['PP']] for idx, row in elecciones.iterrows()]
HeatMap(heat_data_pp).add_to(m)

m
Out[14]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Otra forma de representar el mapa de calor a escala nacional.

In [15]:
import scipy.ndimage

# Creamos una nueva columna con el conteo total de votos por municipio
elecciones['Total_Votos'] = elecciones[columnas_partidos].sum(axis=1)

def heatmap(d, bins=(100, 100), smoothing=1.9, cmap='jet', field='Total_Votos', figsize=(10, 10)):
    def getx(pt):
        return pt.centroid.x

    def gety(pt):
        return pt.centroid.y

    x = list(d.geometry.apply(getx))
    y = list(d.geometry.apply(gety))
    heatmap, xedges, yedges = np.histogram2d(y, x, bins=bins, weights=d[field])
    extent = [yedges[0], yedges[-1], xedges[-1], xedges[0]]

    logheatmap = np.log(heatmap + 0.001)
    logheatmap[np.isneginf(logheatmap)] = 0
    logheatmap = scipy.ndimage.filters.gaussian_filter(logheatmap, smoothing, mode='nearest')

    # tamaño de la figura
    plt.figure(figsize=figsize)

    plt.imshow(logheatmap, cmap=cmap, extent=extent, origin='upper', aspect='auto')
    plt.colorbar()
    plt.gca().invert_yaxis()
    plt.show()

heatmap(elecciones, field='Total_Votos', figsize=(10, 7))
/tmp/ipykernel_44426/4149787150.py:20: DeprecationWarning: Please use `gaussian_filter` from the `scipy.ndimage` namespace, the `scipy.ndimage.filters` namespace is deprecated.
  logheatmap = scipy.ndimage.filters.gaussian_filter(logheatmap, smoothing, mode='nearest')
No description has been provided for this image

AGRUPAMOS LOS DATOS ELECTORALES CON LAS PROVINCIAS (shapefile)¶

Uniremos elecciones con un shapefile de provincias para mostrar resultados a escala provincial. En primer lugar, vamos a crear una nueva columna llamada "Total_Votos" con la que vamos a realizar cálculos más adelante.

In [16]:
# Agrupamos por provincia y sumar los votos para cada partido
elecciones_provi = elecciones.groupby(['Código de Provincia', 'Nombre de Provincia'])[columnas_partidos + ['Total_Votos']].sum()

# Reseteamos el índice para obtener columnas 'Código de Provincia' y 'Nombre de Provincia'
elecciones_provi.reset_index(inplace=True)

# Mostramos el nuevo df
elecciones_provi
Out[16]:
Código de Provincia Nombre de Provincia PP PSOE VOX SUMAR ERC JxCAT - JUNTS EH Bildu EAJ-PNV ... Ud.Ca GITV PUEDE EVC LB UNIDOS SI +RDS+ CCD FUERZA CÍVICA Total_Votos
0 01 Araba / Álava 29973 46479 6509 21390 0 0 32833 27957 ... 0 0 0 0 0 0 0 0 0 167304
1 02 Albacete 88071 75985 36640 15692 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 218503
2 03 Alicante / Alacant 329167 286424 145562 114973 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 888631
3 04 Almería 130740 92233 68007 21157 0 0 0 0 ... 0 0 0 0 194 0 0 0 0 316006
4 05 Ávila 42105 26481 14985 4948 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 96470
5 06 Badajoz 147688 152923 53306 26576 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 386842
6 08 Barcelona 363888 945932 200190 402505 326396 256121 0 0 ... 0 0 0 0 0 0 0 0 0 2625437
7 09 Burgos 81639 69060 25578 17250 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 198616
8 10 Cáceres 89227 91069 31868 16174 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 232620
9 11 Cádiz 220884 210411 96329 81527 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 627167
10 12 Castellón / Castelló 108003 99969 48748 43844 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 303870
11 13 Ciudad Real 114421 99769 45927 17247 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 279684
12 14 Córdoba 169435 143384 62283 61078 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 442151
13 15 A Coruña 288026 188184 33581 81385 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 662459
14 16 Cuenca 45159 42358 17660 6282 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 112472
15 17 Girona 31066 92709 22548 35090 47229 62915 0 0 ... 0 0 0 0 0 0 0 0 0 317740
16 18 Granada 184579 164401 80477 57837 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 494203
17 19 Guadalajara 50996 46186 27014 12800 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 139031
18 20 Gipuzkoa 32024 86666 7670 39443 0 0 116378 84391 ... 0 0 0 0 0 0 0 0 0 370320
19 21 Huelva 93388 92081 37490 26613 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 253713
20 22 Huesca 45562 39899 15073 13624 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 117735
21 23 Jaén 137180 133280 54223 29208 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 364706
22 24 León 100327 91239 34981 18147 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 269759
23 25 Lleida 23436 53850 12449 14413 34067 32982 0 0 ... 0 0 0 0 0 0 0 0 0 180770
24 26 La Rioja 79113 61862 16876 11309 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 171589
25 27 Lugo 98106 59116 8327 10052 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 193699
26 28 Madrid 1444177 994188 499911 550581 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 3535749
27 29 Málaga 300152 237392 129020 95732 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 775255
28 30 Murcia 307009 187997 162600 71034 0 0 0 0 ... 0 0 0 0 0 0 164 0 0 739321
29 31 Navarra 56489 92882 19257 43591 0 0 58692 0 ... 0 0 0 0 0 0 0 0 0 336216
30 32 Ourense 90165 54859 8411 9789 0 0 0 0 ... 0 0 0 0 0 0 0 99 0 179460
31 33 Asturias 209585 202287 73410 87610 0 0 0 0 ... 0 0 256 0 0 0 0 0 0 582385
32 34 Palencia 40454 33351 12417 5792 0 0 0 0 ... 0 361 0 0 0 0 0 0 0 95377
33 36 Pontevedra 223339 177392 26674 74596 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 559676
34 37 Salamanca 92455 59795 28946 10757 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 194989
35 39 Cantabria 145994 115564 48768 29267 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 343495
36 40 Segovia 39713 26916 12446 7054 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 87194
37 41 Sevilla 352296 386355 140529 147682 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 1043832
38 42 Soria 18648 14776 4895 1679 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 49823
39 43 Tarragona 50747 120468 37897 41488 55198 40591 0 0 ... 0 0 0 261 0 245 0 0 0 362891
40 44 Teruel 26497 22048 9900 4081 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 75074
41 45 Toledo 146316 125499 75895 31607 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 383100
42 46 Valencia / València 481570 459018 217713 240935 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 1419017
43 47 Valladolid 128499 102860 47697 27693 0 0 0 0 ... 452 0 0 0 0 0 0 0 113 311740
44 48 Bizkaia 69771 156641 15760 66189 0 0 125418 163511 ... 0 0 0 0 0 0 0 0 0 602806
45 49 Zamora 45550 32996 13448 5730 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 100766
46 50 Zaragoza 185575 158563 78869 69221 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 510009

47 rows × 62 columns

Realizamos la union

In [17]:
provi_shp = gpd.read_file('Provincias/ProvinciasETRS8930N.shp')
elecciones_provi_shp = gpd.GeoDataFrame(provi_shp.merge(elecciones_provi, left_on='IDPROV', right_on='Código de Provincia'))
elecciones_provi_shp.head()
Out[17]:
IDPROV Texto Texto_Alt Cod_CCAA CCAA geometry Código de Provincia Nombre de Provincia PP PSOE ... Ud.Ca GITV PUEDE EVC LB UNIDOS SI +RDS+ CCD FUERZA CÍVICA Total_Votos
0 01 Álava Araba 16 País Vasco POLYGON ((497823.750 4784874.150, 498456.775 4... 01 Araba / Álava 29973 46479 ... 0 0 0 0 0 0 0 0 0 167304
1 02 Albacete Albacete 08 Castilla - La Mancha POLYGON ((630529.311 4364287.687, 630118.311 4... 02 Albacete 88071 75985 ... 0 0 0 0 0 0 0 0 0 218503
2 03 Alicante Alacant 10 Comunitat Valenciana MULTIPOLYGON (((720290.776 4227118.177, 720292... 03 Alicante / Alacant 329167 286424 ... 0 0 0 0 0 0 0 0 0 888631
3 04 Almería Almería 01 Andalucía MULTIPOLYGON (((572241.564 4064120.709, 572219... 04 Almería 130740 92233 ... 0 0 0 0 194 0 0 0 0 316006
4 05 Ávila Ávila 07 Castilla y León POLYGON ((331047.917 4558945.287, 331286.923 4... 05 Ávila 42105 26481 ... 0 0 0 0 0 0 0 0 0 96470

5 rows × 68 columns

In [18]:
#elecciones_provi_shp.info()

ESTUDIO PRELIMINAR DE LOS DATOS¶

MAPA SENCILLO CON LA CANTIDAD DE VOTOS POR PROVINCIA¶

In [19]:
# Configuramos la figura y los ejes
fig, ax = plt.subplots(1, figsize=(15, 10))
ax.set_title('Cantidad de votos por provincias', fontdict={'fontsize': '20', 'fontweight': '3'})

# Visualizamos el mapa 
elecciones_provi_shp.plot(ax=ax, column='Total_Votos', cmap='YlOrRd', linewidth=0.5, edgecolor='black')

# leyenda
cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])  # coord y dimensiones del colorbar
sm = plt.cm.ScalarMappable(cmap='YlOrRd', norm=plt.Normalize(vmin=elecciones_provi_shp['Total_Votos'].min(), vmax=elecciones_provi_shp['Total_Votos'].max()))
sm._A = []  
cbar = fig.colorbar(sm, cax=cax, format="%d")
cbar.set_label('Total Votos por Provincia', rotation=270, labelpad=15, fontsize=15)

# Desactivar los ejes
ax.set_axis_off()

plt.show()
No description has been provided for this image

MATRIZ DE CORRELACIÓN¶

Vamos a representar una matriz de correlación para mostrar los partidos políticos que más se votan en función de la provincia.

Parámetros de la función pivot_table de pandas: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.pivot_table.html

In [20]:
# Filtramos por las columnas relevantes (comunidades autónomas y partidos grandes)
df_filtered = j23[['Nombre de Comunidad', 'PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS', 'EH Bildu', 'EAJ-PNV','B.N.G.','U.P.N.']]

# Para contar las frecuencias de cada combinación de partido y comunidad autónoma
pivot_table = pd.pivot_table(df_filtered, values=['PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS', 'EH Bildu', 'EAJ-PNV','B.N.G.','U.P.N.'], index='Nombre de Comunidad', aggfunc='sum', fill_value=0)

# Ploteamos
plt.figure(figsize=(19, 8))
sns.heatmap(pivot_table, cmap="YlGnBu", annot=True, fmt='g', cbar_kws={'label': 'Número de Votos'})
plt.title('Matriz de Correlación entre Número de Votos de Partidos Grandes y Comunidades Autónomas')
plt.xlabel('Partidos Grandes')
plt.ylabel('Comunidades Autónomas')
plt.xticks(rotation=45, ha='right')
plt.show()
No description has been provided for this image

Como cabe esperar, el Partido Popular y el PSOE, que son partidos más extendidos y votados con diferencia, tienen representación en prácticamente toda la península. Sin embargo, en regiones como Navarra, País Vasco o La Rioja (pocos votos debido a su baja población), estos dos partidos tienen menos representación ya que otros partidos regionalistas son más votados, como por ejemplo el PNV (Partido Nacionalista Vasco) o EH Bildu en País Vasco, el BNG (Bloque Nacionalista Gallego) en Galicia (pese a que este último tiene muchos votos y representación, el Partido Popular gana en esta región). Por otro lado, en Cataluña, partidos regionalistas como ERC (Esquerra Republicana de Catalunya) tienen más representación que el Partido Popular.

GRÁFICO DE BARRAS¶

A continuación, vamos a representar de diferentes formas los resultados electorales para sacar nuevas conclusiones

In [21]:
# Obtenemos la suma de votos para cada partido
sumas_por_partido = elecciones_provi_shp.iloc[:, 8:67].sum()

# Definimos un umbral para agrupar partidos pequeños en "Otros"
umbral_otro = 50000  # Puedes ajustar este valor según tu preferencia

# Filtrar los partidos grandes y los pequeños
partidos_grandes = sumas_por_partido[sumas_por_partido >= umbral_otro]
otros_partidos = sumas_por_partido[sumas_por_partido < umbral_otro]

# Colores personalizados para los grandes partidos
colores_partidos = {
    'PP': '#11A0E8',
    'PSOE': '#F32020',
    'VOX': '#52D73E',
    'SUMAR': '#FF0066',
    'ERC': '#F0D733',
    'JxCAT - JUNTS': '#33F0B7',
    'EH Bildu': '#A7E55F',
    'EAJ-PNV': '#AB7D39',
    'B.N.G.': '#66CEDA',
    'U.P.N.': '#164CDF',
    'CCa': '#FFCD00',
}

# Crear un gráfico de barras estilizado
plt.figure(figsize=(15, 8))

# Barra para los grandes partidos
for partido, color in colores_partidos.items():
    if partido in partidos_grandes.index:
        plt.bar(partido, partidos_grandes[partido], color=color, label=f'{partido} ({partidos_grandes[partido]:,} votos)')

# Barra para "Otros"
plt.bar('Otros', otros_partidos.sum(), color='lightgray', label=f'Otros Partidos ({otros_partidos.sum():,} votos)')

# Agregar etiquetas y título
plt.title('Suma de Votos por Partido en Todas las Provincias', fontsize=18)
plt.xlabel('Partido Político', fontsize=14)
plt.ylabel('Total de Votos', fontsize=14)
plt.xticks(rotation=45, ha='right', fontsize=12)
plt.yticks(fontsize=12)

# Mostrar la leyenda con estilo
plt.legend(frameon=False, bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=10)

plt.show()
No description has been provided for this image

En esta gráfica podemos ver los partidos políticos que y su recuento de votos

DONUT CHART¶

In [22]:
# Obtenemos la suma de votos para cada partido
sumas_por_partido = elecciones_provi_shp.iloc[:, 8:67].sum()

# Definimos un umbral para agrupar partidos pequeños en "Otros"
umbral_otro = 50000  #menos de 50k votos entrarán en la categoría "Otros"

# Filtramos los partidos grandes y los pequeños
partidos_grandes = sumas_por_partido[sumas_por_partido >= umbral_otro]
otros_partidos = sumas_por_partido[sumas_por_partido < umbral_otro]

# Colores personalizados para los grandes partidos
colores_partidos = {
    'PP': '#11A0E8',
    'PSOE': '#F32020',
    'VOX': '#52D73E',
    'SUMAR': '#FF0066',
    'ERC': '#F0D733',
    'JxCAT - JUNTS': '#33F0B7',
    'EH Bildu': '#A7E55F',
    'EAJ-PNV': '#AB7D39',
    'B.N.G.': '#66CEDA',
    'U.P.N.': '#164CDF',
    'CCa': '#FFCD00',
}

# gráfico circular 
plt.figure(figsize=(10, 10))

# Datos para el gráfico circular
sizes = partidos_grandes.values
labels = partidos_grandes.index

# Colores y explotar para resaltar el primer sector
colors = [colores_partidos.get(label, 'lightgray') for label in labels]
explode = [0.1 if label == partidos_grandes.idxmax() else 0 for label in labels]

# Crear el gráfico circular
plt.pie(sizes, labels=None, autopct=None,
        colors=colors, startangle=140, explode=explode, wedgeprops=dict(width=0.4))

# Leyenda
plt.legend(labels=labels, loc="center left", bbox_to_anchor=(1, 0.5), title='Partidos', title_fontsize=14)

# Agregar título
plt.title('Distribución de Votos por Partido', fontsize=18)

plt.show()
No description has been provided for this image

GRÁFICOS DE BARRAS MÁS ESPECÍFICOS¶

A continuación, vamos a representar gráficos de barras para comparar de cómo han sido los resultados en determinadas provincias y su municipio principal. Utilizaremos de ejemplo Madrid y Barcelona.

MADRID¶

In [23]:
# Convertimos columnas a tipo numérico en Madrid provincia
madrid_provincia = elecciones_provi_shp[elecciones_provi_shp['Nombre de Provincia'] == 'Madrid']
madrid_provincia.iloc[:, 8:67] = madrid_provincia.iloc[:, 8:67].apply(pd.to_numeric, errors='coerce')

# Convertimos columnas a tipo numérico en Madrid municipio
madrid_municipio = elecciones[elecciones['Nombre de Municipio'] == 'Madrid']
madrid_municipio.iloc[:, 13:73] = madrid_municipio.iloc[:, 13:73].apply(pd.to_numeric, errors='coerce')

# suma de votos para cada partido en Madrid provincia y municipio
votos_madrid_provincia = madrid_provincia.iloc[:, 8:67].sum()
votos_madrid_municipio = madrid_municipio.iloc[:, 13:73].sum()

# Umbral para partidos pequeños en "Otros"
umbral_otro = 50000  

# Filtramos los partidos grandes y los pequeños para Madrid provincia
partidos_grandes_provincia = votos_madrid_provincia[votos_madrid_provincia >= umbral_otro]
otros_partidos_provincia = votos_madrid_provincia[votos_madrid_provincia < umbral_otro]

# Filtramos los partidos grandes y los pequeños para Madrid municipio
partidos_grandes_municipio = votos_madrid_municipio[votos_madrid_municipio >= umbral_otro]
otros_partidos_municipio = votos_madrid_municipio[votos_madrid_municipio < umbral_otro]

# Colores personalizados para los grandes partidos
colores_partidos = {
    'PP': '#11A0E8',
    'PSOE': '#F32020',
    'VOX': '#52D73E',
    'SUMAR': '#FF0066',
    'ERC': '#F0D733',
    'JxCAT - JUNTS': '#33F0B7',
    'EH Bildu': '#A7E55F',
    'EAJ-PNV': '#AB7D39',
    'B.N.G.': '#66CEDA',
    'U.P.N.': '#164CDF',
    'CCa': '#FFCD00',
}

# Creamos dos subplots
fig, (ax_provincia, ax_municipio) = plt.subplots(1, 2, figsize=(12, 6))

# gráfica Madrid provincia
x_provincia = np.arange(len(partidos_grandes_provincia))
ax_provincia.barh(x_provincia, partidos_grandes_provincia.values, color=[colores_partidos.get(label, 'lightgray') for label in partidos_grandes_provincia.index])
ax_provincia.set_yticks(x_provincia)
ax_provincia.set_yticklabels(partidos_grandes_provincia.index, fontsize=10)
ax_provincia.set_xlabel('Número de Votos', fontsize=12)
ax_provincia.set_title('Votos en Partidos Grandes en La Comunidad de Madrid', fontsize=14)


# gráfica Madrid municipio
x_municipio = np.arange(len(partidos_grandes_municipio))
ax_municipio.barh(x_municipio, partidos_grandes_municipio.values, color=[colores_partidos.get(label, 'lightgray') for label in partidos_grandes_municipio.index])
ax_municipio.set_yticks(x_municipio)
ax_municipio.set_yticklabels(partidos_grandes_municipio.index, fontsize=10)
ax_municipio.set_xlabel('Número de Votos', fontsize=12)
ax_municipio.set_title('Votos en Partidos Grandes en Madrid Municipio', fontsize=14)


plt.tight_layout()
plt.show()
No description has been provided for this image

BARCELONA¶

In [24]:
# Convertimos columnas a tipo numérico en Barcelona provincia
barcelona_provincia = elecciones_provi_shp[elecciones_provi_shp['Nombre de Provincia'] == 'Barcelona']
barcelona_provincia.iloc[:, 8:67] = barcelona_provincia.iloc[:, 8:67].apply(pd.to_numeric, errors='coerce')

# Convertimos columnas a tipo numérico en Barcelona municipio
barcelona_municipio = elecciones[elecciones['Nombre de Municipio'] == 'Barcelona']
barcelona_municipio.iloc[:, 13:73] = barcelona_municipio.iloc[:, 13:73].apply(pd.to_numeric, errors='coerce')

# Obtenemos la suma de votos para cada partido en Barcelona provincia y municipio
votos_barcelona_provincia = barcelona_provincia.iloc[:, 8:67].sum()
votos_barcelona_municipio = barcelona_municipio.iloc[:, 13:73].sum()

# umbral para partidos pequeños en "Otros"
umbral_otro = 50000 

# Filtramos los partidos grandes y los pequeños para Barcelona provincia
partidos_grandes_provincia = votos_barcelona_provincia[votos_barcelona_provincia >= umbral_otro]
otros_partidos_provincia = votos_barcelona_provincia[votos_barcelona_provincia < umbral_otro]

# Filtramos los partidos grandes y los pequeños para Barcelona municipio
partidos_grandes_municipio = votos_barcelona_municipio[votos_barcelona_municipio >= umbral_otro]
otros_partidos_municipio = votos_barcelona_municipio[votos_barcelona_municipio < umbral_otro]

# Colores personalizados para los grandes partidos
colores_partidos = {
    'PP': '#11A0E8',
    'PSOE': '#F32020',
    'VOX': '#52D73E',
    'SUMAR': '#FF3982',
    'ERC': '#F0D733',
    'JxCAT - JUNTS': '#33F0B7',
    'EH Bildu': '#A7E55F',
    'EAJ-PNV': '#AB7D39',
    'B.N.G.': '#66CEDA',
    'U.P.N.': '#164CDF',
    'CCa': '#FFCD00',
}

# Creamos dos subgráficas en horizontal
fig, (ax_provincia, ax_municipio) = plt.subplots(1, 2, figsize=(12, 6))

# Barcelona provincia
x_provincia = np.arange(len(partidos_grandes_provincia))
ax_provincia.barh(x_provincia, partidos_grandes_provincia.values, color=[colores_partidos.get(label, 'lightgray') for label in partidos_grandes_provincia.index])
ax_provincia.set_yticks(x_provincia)
ax_provincia.set_yticklabels(partidos_grandes_provincia.index, fontsize=10, rotation=45, ha='right')  # Rotar etiquetas
ax_provincia.set_xlabel('Número de Votos', fontsize=12)
ax_provincia.set_title('Votos en Partidos Grandes en Barcelona Provincia', fontsize=14)


# Barcelona municipio
x_municipio = np.arange(len(partidos_grandes_municipio))
ax_municipio.barh(x_municipio, partidos_grandes_municipio.values, color=[colores_partidos.get(label, 'lightgray') for label in partidos_grandes_municipio.index])
ax_municipio.set_yticks(x_municipio)
ax_municipio.set_yticklabels(partidos_grandes_municipio.index, fontsize=10, rotation=45, ha='right')  # Rotar etiquetas
ax_municipio.set_xlabel('Número de Votos', fontsize=12)
ax_municipio.set_title('Votos en Partidos Grandes en Barcelona Municipio', fontsize=14)

plt.tight_layout()
plt.show()
No description has been provided for this image

RESULTADOS ELECTORALES POR PROVINCIA¶

Al igual que el mapa anterior que mostraba los resultados electorales en cada municipio a escala nacional, ahora mostramos más de cerca y podemos visualizar los resultados a nivel de provincia.

In [60]:
# Filtramos Barcelona
municipios_barcelona = elecciones[elecciones['Nombre de Provincia'] == 'Barcelona']

# mapa Barcelona
barcelona_mapa = municipios_barcelona.plot(figsize=(12, 12),linewidth=0.3, edgecolor='0.1', color=[colores_partidos.get(partido, 'lightgray') for partido in municipios_barcelona['Partido_Mas_Votado']])

# leyenda
patches = [plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=colores_partidos.get(partido, 'lightgray'), markersize=10, label=partido) for partido in colores_partidos.keys()]
barcelona_mapa.legend(handles=patches, title='Partido Más Votado', loc='upper left', bbox_to_anchor=(1, 1))

# plot mapa
plt.title('Partidos más votados en Barcelona')
plt.show()
No description has been provided for this image
In [59]:
# Filtramos Madrid
municipios_barcelona = elecciones[elecciones['Nombre de Provincia'] == 'Madrid']

# mapa 
barcelona_mapa = municipios_barcelona.plot(figsize=(12, 12),linewidth=0.3, edgecolor='0', color=[colores_partidos.get(partido, 'lightgray') for partido in municipios_barcelona['Partido_Mas_Votado']])

#  leyenda
patches = [plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=colores_partidos.get(partido, 'lightgray'), markersize=10, label=partido) for partido in colores_partidos.keys()]
barcelona_mapa.legend(handles=patches, title='Partido Más Votado', loc='upper left', bbox_to_anchor=(1, 1))

# plot mapa
plt.title('Partidos más votados en la Comunidad de Madrid')
plt.show()
No description has been provided for this image

BUSCA LA PROVINCIA QUE QUIERAS¶

En este apartado podremos indicar la provincia que queremos que se represente (únicamente funciona en el jupyter notebook, no en el HTML). De tal modo, que podremos escribir la provincia que queramos ver representada en base al listado de provincias que se imprime.

In [58]:
#Print de las Provincias
print("Nombres de todas las Provincias:")
print(elecciones['Nombre de Provincia'].unique())

# Pedir al usuario que elija una provincia
provincia_elegida = input("Ingrese el nombre de la provincia para ver los resultados electorales: ")

# Filtramos municipios de la provincia elegida
municipios_provincia = elecciones[elecciones['Nombre de Provincia'] == provincia_elegida]

# Verificamos si la provincia elegida existe en los datos (chequear la lista)
if municipios_provincia.empty:
    print(f"No hay datos disponibles para la provincia de {provincia_elegida}.")
else:
    # Creamos un mapa centrado en la provincia elegida
    provincia_mapa = municipios_provincia.plot(figsize=(12, 12), linewidth=0.3, edgecolor='0.2',
                                                color=[colores_partidos.get(partido, 'lightgray') for partido in municipios_provincia['Partido_Mas_Votado']])

    # leyenda
    patches = [plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=colores_partidos.get(partido, 'lightgray'),
                          markersize=10, label=partido) for partido in colores_partidos.keys()]
    provincia_mapa.legend(handles=patches, title='Partido Más Votado', loc='upper left', bbox_to_anchor=(1, 1))

    # plot
    plt.title(f'Partidos más votados en {provincia_elegida}')
    plt.show()
Nombres de todas las Provincias:
['Almería' 'Cádiz' 'Córdoba' 'Granada' 'Huelva' 'Jaén' 'Málaga' 'Sevilla'
 'Huesca' 'Teruel' 'Zaragoza' 'Asturias' 'Cantabria' 'Albacete'
 'Ciudad Real' 'Cuenca' 'Guadalajara' 'Toledo' 'Ávila' 'Burgos' 'León'
 'Palencia' 'Salamanca' 'Segovia' 'Soria' 'Valladolid' 'Zamora'
 'Barcelona' 'Girona' 'Lleida' 'Tarragona' 'Badajoz' 'Cáceres' 'A Coruña'
 'Lugo' 'Ourense' 'Pontevedra' 'Madrid' 'Navarra' 'Araba / Álava'
 'Gipuzkoa' 'Bizkaia' 'Murcia' 'La Rioja' 'Alicante / Alacant'
 'Castellón / Castelló' 'Valencia / València']
No description has been provided for this image

ANÁLISIS EXPLORATORIO DE LOS DATOS¶

A partir de aquí realizaremos tratamiento estadístico de nuestro dataset. pandas, matplotlib y seaborn serán esenciales para el análsis exploratorio de los datos (EDA)

BOXPLOT¶

In [28]:
# Filtramos los partidos más importantes
partidos_importantes = ['PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS']
elecciones_partidos_importantes = elecciones[partidos_importantes]

# Estilo de Seaborn
sns.set(style="whitegrid")

# boxplot
plt.figure(figsize=(12, 8))
ax = sns.boxplot(data=elecciones_partidos_importantes, palette='Set3', width=0.7)
ax.set_title('Boxplot de Votos para Partidos Importantes por municipios', fontsize=16)
ax.set_xlabel('Partidos', fontsize=14)
ax.set_ylabel('Votos', fontsize=14)
ax.tick_params(axis='both', labelsize=12)

plt.xticks(rotation=45, ha='right')  
plt.tight_layout()  

plt.show()
No description has been provided for this image
In [29]:
# Filtrar los partidos más importantes
partidos_importantes = ['PP', 'PSOE', 'VOX', 'SUMAR', 'ERC', 'JxCAT - JUNTS']
elecciones_partidos_importantes = elecciones[partidos_importantes]

# Estilo de Seaborn
sns.set(style="whitegrid")

# Creamos un boxplot excluyendo valores atípicos para Madrid y Barcelona
plt.figure(figsize=(12, 8))
ax = sns.boxplot(data=elecciones_partidos_importantes, palette='Set3', width=0.7, showfliers=False)

ax.set_title('Boxplot de Votos para partidos por municipio (Elimina los valores atípicos)', fontsize=16)
ax.set_xlabel('Partidos', fontsize=14)
ax.set_ylabel('Votos', fontsize=14)
ax.tick_params(axis='both', labelsize=12)

plt.xticks(rotation=45, ha='right')  # Rotar etiquetas del eje x 
plt.tight_layout()  

plt.show()
No description has been provided for this image

Generamos este boxplot para visualizar la distribución de votos para los partidos políticos más importantes.

HISTOGRAMAS¶

Agrupamos a nivel de provincia para representar los histogramas.

In [30]:
# Seleccionamos datos de los cuatro principales partidos
partidos_principales = ['PP', 'PSOE', 'VOX', 'SUMAR']
datos_partidos = elecciones_provi_shp[partidos_principales]

# estilo de Seaborn
sns.set(style="whitegrid")

# histograma para cada partido
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Histogramas de Votos para los Cuatro Principales Partidos', fontsize=16)

for i, partido in enumerate(partidos_principales):
    ax = axes[i // 2, i % 2]
    sns.histplot(datos_partidos[partido], bins=20, kde=True, color='skyblue', ax=ax)
    ax.set_title(partido)
    ax.set_xlabel('Votos')
    ax.set_ylabel('Frecuencia')

plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
No description has been provided for this image

Estos 4 histogramas representan, en función del número de votos, la cantidad de provincias en las que se repite dicha la cantidad de votos. Cada gráfico es como un contenedor que muestra cuántas provincias tuvieron cierta cantidad de votos para un partido específico.

  • PP: Como observamos en el primer histograma, la repartición de los votos en del Partido Popular se podría interpretar como un partido que recibe más votos en regiones donde la cantidad de votos es reducida, es decir, en poblaciones pequeñas y regiones de la península con poca población.

  • PSOE: En el histograma del PSOE podemos observar que el factor geográfico no le afecta tanto, puesto que podemos interpretar que la repartición de los votos recibidos al Partido Socialista es más uniforme en toda la península que el Partido Popular. Además, el PSOE tiene mayor representación en las provincias de mayor población.

  • VOX: El histograma de Vox sugiere que la mayoría de las frecuencias de votos a este partido se concentran en la categoría de 0 a 100,000 votos, indicando que en la mayoría de los lugares, VOX obtuvo un número de votos más bajo, mientras que hay algunos lugares donde alcanzó un rango de 100,000 a 200,000 votos, sugiriendo un apoyo significativo en esos pocos lugares, es decir, en entornos rurales y similares.

  • SUMAR: La mayoría de las frecuencias de votos para SUMAR se concentran en la categoría de 0 a 100,000 votos. Además, se observa que hay unos pocos lugares donde SUMAR recibió más de 100,000 votos, pero la distribución es más dispersa en comparación con los otros partidos principales a lo largo del eje x, lo que sugiere una presencia más equitativa en términos de votos en diferentes rangos en esos lugares específicos. Es decir, los votos a SUMAR se agrupan en las regiones con poblaciones grandes.

ANÁLISIS DE PATRONES¶

¿Sigue una distribución?¶

Con el total de votos

In [31]:
import statsmodels.api as sm

# Seleccionamos los datos de Total_Votos
total_votos = elecciones_provi_shp['Total_Votos']

# QQ plot
sm.qqplot(total_votos, line='s')

# título y etiquetas
plt.title('Gráfico QQ - Normalidad de Total_Votos')
plt.xlabel('Cuantiles teóricos (distribución normal estándar)')
plt.ylabel('Cuantiles observados')

# plot
plt.show()
No description has been provided for this image

Si los puntos en el gráfico QQ no se ajustan a la línea diagonal, los datos no se asemejan a una distribución normal. Sin embargo, este método es exploratorio y no prueba la normalidad de manera rigurosa.

Con el PP y PSOE

In [32]:
# Seleccionamos los datos de votos del PP y del PSOE
votos_pp = elecciones_provi_shp['PP']
votos_psoe = elecciones_provi_shp['PSOE']

# Creamos gráficos en una fila horizontal
fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# QQ plot PP
sm.qqplot(votos_pp, line='s', ax=axes[0])
axes[0].set_title('Gráfico QQ - Votos del PP')

# QQ plot PSOE
sm.qqplot(votos_psoe, line='s', ax=axes[1])
axes[1].set_title('Gráfico QQ - Votos del PSOE')

# plot
plt.tight_layout()
plt.show()
No description has been provided for this image

Si los puntos en los gráficos QQ no se ajustan a la línea diagonal, los datos no se asemejan a una distribución normal.

QQ plot se utiliza para evaluar si una muestra de datos sigue una distribución normal. Si los puntos del gráfico se alejan significativamente de la línea diagonal en ciertas regiones, puede indicar desviaciones de la normalidad en esos cuantiles. El gráfico del PP se asemeja más a lo que sería una distribución normal respecto al PSOE. El PSOE, al estar más representado en regiones de mayor población que el PP, presenta más sesgo que su rival.

PRUEBA DE Kolmogórov-Smirnov¶

In [33]:
from scipy.stats import ks_2samp, norm

# Seleccionamos datos de votos del PP, PSOE y Total_Votos
votos_pp = elecciones_provi_shp['PP']
votos_psoe = elecciones_provi_shp['PSOE']
total_votos = elecciones_provi_shp['Total_Votos']

# Función para realizar la prueba de Kolmogórov-Smirnov
def prueba_ks(datos, variable):
    # Generamos muestras aleatorias de una distribución normal
    media = datos.mean()
    desviacion_estandar = datos.std()
    muestras_normales = norm.rvs(loc=media, scale=desviacion_estandar, size=len(datos), random_state=36)
    
    # prueba de km
    estadistico_ks, p_valor = ks_2samp(datos, muestras_normales)
    
    # Imprimir resultados
    print(f'Prueba de Kolmogórov-Smirnov para {variable}:')
    print(f'Estadístico KS: {estadistico_ks}')
    print(f'P-valor: {p_valor}')
    
    # Verificamos la hipótesis nula
    alpha = 0.05
    if p_valor > alpha:
        print(f'No se rechaza la hipótesis nula. {variable} sigue una distribución normal. \n' )
    else:
        print(f'Se rechaza la hipótesis nula. {variable} no sigue una distribución normal. \n' )

# Aplicamos la prueba para cada variable
prueba_ks(votos_pp, 'votos del PP')
prueba_ks(votos_psoe, 'votos del PSOE')
prueba_ks(total_votos, 'Total_Votos')
Prueba de Kolmogórov-Smirnov para votos del PP:
Estadístico KS: 0.3404255319148936
P-valor: 0.008205525490630283
Se rechaza la hipótesis nula. votos del PP no sigue una distribución normal. 

Prueba de Kolmogórov-Smirnov para votos del PSOE:
Estadístico KS: 0.2978723404255319
P-valor: 0.03035107595148269
Se rechaza la hipótesis nula. votos del PSOE no sigue una distribución normal. 

Prueba de Kolmogórov-Smirnov para Total_Votos:
Estadístico KS: 0.3191489361702128
P-valor: 0.016154625944318796
Se rechaza la hipótesis nula. Total_Votos no sigue una distribución normal. 

Si el p valor está por debajo de su umbral de significancia (normalmente p < 0,05), entonces se puede rechazar la hipótesis nula, pero esto no significa necesariamente que su hipótesis alternativa sea cierta.

Un p valor de 0,05 es estadísticamente significativo, y un p valor de 0,001 es estadísticamente muy significativo (menos de una posibilidad entre mil de estar equivocado). En segundo lugar, un valor p de 0,05 codifica un nivel de significancia del 5% y un nivel de confianza de 1 - 0,05 = 0,95, o 95%. En nuestro caso, el p valor no está muy cerca de 0, y no hay pruebas suficientes para rechazar la hipótesis nula.

Se aplican las pruebas para cada variable. En los resultados, se observa que el p-valor es menor a 0.05 en todos los casos, lo que lleva a rechazar la hipótesis nula de que los datos siguen una distribución normal. Por lo tanto, según la prueba de KS, se concluye que los votos del PP, votos del PSOE y el total de votos no siguen una distribución normal. La interpretación del p-valor es que existe evidencia significativa en contra de la hipótesis nula de normalidad.

Establecer una semilla particular (semilla "36") es útil cuando se desea que los resultados de operaciones de generación de números aleatorios sean consistentes cada vez que se ejecute el código. Esto es especialmente importante en el contexto de la investigación científica y el desarrollo de modelos, donde la reproducibilidad es crucial.

VARIOGRAMAS Y SEMIVARIOGRAMAS¶

Los variogramas miden la variabilidad o dispersión de los valores de una variable en función de la distancia entre ubicaciones espaciales. Los semivariogramas, por otro lado, son la mitad del variograma y representa la mitad de la varianza de la diferencia entre los valores de dos ubicaciones espaciales a medida que la distancia entre ellas aumenta.

In [34]:
# Creamos subplots (para los variogramas)
fig, axs = plt.subplots(1, 3, figsize=(18, 6))

# Iterar sobre cada variable y crear variograma
for i, variable in enumerate(['Total_Votos', 'PSOE', 'PP']):
    # Cambiar la "geometría" de "polígono" a "centroide" (punto central del polígono)
    elecciones_provi_shp["centroid"] = elecciones_provi_shp.centroid
    elecciones_provi_shp['x'] = elecciones_provi_shp['centroid'].x
    elecciones_provi_shp['y'] = elecciones_provi_shp['centroid'].y

    coordinates = elecciones_provi_shp[['x', 'y']].values
    list_values = elecciones_provi_shp[variable].values

    # Creamos un nuevo subplot para cada variable (variogramas)
    ax_variogram = axs[i]

    # Cálculo del variograma
    V = skg.Variogram(coordinates=coordinates, values=list_values, model='spherical', use_nugget=True, n_lags=10)

    # Visualización del variograma
    V.distance_difference_plot(ax=ax_variogram)
    V.plot()

    # título del subplot
    ax_variogram.set_title(f'Variogram - {variable}')

# diseño 
plt.tight_layout()

# plot
plt.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_dd_plot.py:46: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_plot.py:123: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_dd_plot.py:46: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_plot.py:123: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_dd_plot.py:46: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/skgstat/plotting/variogram_plot.py:123: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  fig.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Interpretación de las gráficas de variograma:

  1. Variograma de 'Total_Votos':

    • Se muestra la variabilidad espacial de la variable 'Total_Votos' en función de la distancia entre las provincias.
    • La línea en el gráfico representa la semivarianza entre los valores de 'Total_Votos' a medida que aumenta la distancia.
    • El punto donde la línea se estabiliza indica la escala típica de variabilidad espacial en 'Total_Votos'.
    • La línea llega a un valor constante (el nugget), esto indica que hay una componente de variabilidad espacial a una escala más pequeña que la resolución de los datos.
  2. Variograma de 'PSOE' y 'PP':

    • Similar al variograma de 'Total_Votos', pero aplicado a las variables 'PSOE' y 'PP' por separado.
    • La variabilidad espacial en 'PSOE' y 'PP' se evalúa en función de la distancia entre las provincias.
    • PP: La línea llega a un valor constante (el nugget), esto indica que hay una componente de variabilidad espacial a una escala más pequeña que la resolución de los datos.
    • PSOE: La semivarianza aumenta a medida que aumenta la distancia, esto indicar una variabilidad espacial.

(Hemos realizado los estudios de los variogramas y semivariogramas a escala provincial debido a que a escala municipal de toda españa no se podían realizar por la magnitud del cálculo con la máquina virtual)

Cálculo del Índice de Autocorrelación I de Moran¶

Técnicas de autocorrelación espacial con el PP (Partido Popular) con mayor cantidad de votos.

In [35]:
import libpysal
from libpysal.weights import Queen #otros pesos: Rook, KNN
w_queen = Queen.from_dataframe(elecciones_provi_shp) 
w_queen.n
Out[35]:
47
In [36]:
w_queen.neighbors[0]
Out[36]:
[18, 7, 24, 44, 29]
In [37]:
elecciones_provi_shp['Nombre de Provincia'][[0, 18, 7, 24, 44, 29]]
Out[37]:
0     Araba / Álava
18         Gipuzkoa
7            Burgos
24         La Rioja
44          Bizkaia
29          Navarra
Name: Nombre de Provincia, dtype: object
In [38]:
elecciones_PP = elecciones_provi_shp['PP']
from esda.moran import Moran
mi = Moran(elecciones_PP, w_queen, permutations=99999)
"%6.4f" % mi.I
Out[38]:
'-0.0429'
In [39]:
#Valor p (aproximación normal estándar de permutaciones)
mi.p_norm
Out[39]:
0.8203878028405664

Calculamos el Índice de Autocorrelación Espacial de Moran (I de Moran) para los votos del PP en las provincias:

  • Creamos una matriz de pesos espaciales usando w_queen como criterio de vecindad. Hay 47 observaciones (provincias) en total.

  • En "Moran(elecciones_PP, w_queen, permutations=99999)" Calculamos el índice de autocorrelación espacial de Moran para los votos del PP utilizando la matriz de pesos espaciales definida anteriormente. El valor de I de Moran es -0.0429.

Interpretación:

  • El valor de I de Moran es -0.0429, lo cual sugiere una autocorrelación espacial baja. Este valor puede oscilar entre -1 (indicando una dispersión espacial uniforme) y 1 (indicando una fuerte autocorrelación espacial).

  • El p valor es 0.8203878028405664, lo cual es bastante alto. Un valor alto sugiere que no hay evidencia suficiente para rechazar la hipótesis nula de ausencia de autocorrelación espacial en los votos del PP.

No hay evidencia significativa de autocorrelación espacial en los votos del Partido Popular entre las provincias.

Resultados del índice de Moran para visualizar la autocorrelación espacial¶

In [40]:
from splot.esda import plot_moran

plot_moran(mi, zstandard=True, figsize=(10,4))
plt.show()
No description has been provided for this image

Como podmos observar, la presencia de valores atípicos (outliers) entorpece una correcta visualización de la distribución. Por lo que, procedemos a eliminarlos para poder sacar mejores conclusiones.

Resultados del índice de Moran para visualizar la autocorrelación espacial (Sin Outlaiers)¶

eliminamos los valores atipicos

In [41]:
# Filtramos las observaciones, excluyendo Madrid y Barcelona
provincias_sin_mb = elecciones_provi_shp[~elecciones_provi_shp['Nombre de Provincia'].isin(['Madrid', 'Barcelona'])]

# Seleccionamos solo los votos del PP
elecciones_PP_limpio = provincias_sin_mb['PP']

# Creamos la matriz de pesos espaciales para las provincias sin Madrid y Barcelona
w_queen_sin_mb = Queen.from_dataframe(provincias_sin_mb)

# índice de Moran
mi_sin_mb = Moran(elecciones_PP_limpio, w_queen_sin_mb, permutations=99999)

# Imprimir el valor de I de Moran
print("%6.4f" % mi_sin_mb.I)

# diagrama de dispersión de Moran
plot_moran(mi_sin_mb, zstandard=True)
0.3108
Out[41]:
(<Figure size 1000x400 with 2 Axes>,
 array([<Axes: title={'center': 'Reference Distribution'}, xlabel='Moran I: 0.31', ylabel='Density'>,
        <Axes: title={'center': 'Moran Scatterplot (0.31)'}, xlabel='Attribute', ylabel='Spatial Lag'>],
       dtype=object))
No description has been provided for this image

Entiendo que el valor resultante del índice de Moran es 0.3108. Este valor indica el grado de autocorrelación espacial en los votos del PP en las provincias excluyendo Madrid y Barcelona. Un valor positivo indica autocorrelación espacial positiva, lo que significa que las provincias con altos niveles de votos del PP tienden a estar rodeadas por provincias con altos niveles de votos a la misma, y viceversa.

Este valor sugiere que hay alguna forma de agrupación espacial en los votos del PP en las provincias, aunque la fuerza de esta autocorrelación no es extremadamente alta.

In [42]:
#Valor p 
mi_sin_mb.p_norm
Out[42]:
0.0006274222904977525

El p valor de 0.0006274 es bastante bajo. Esto sugiere que el índice de Moran calculado para los votos del PP en las provincias, excluyendo Madrid y Barcelona, es estadísticamente significativo. Este valor nos indica que es poco probable que el patrón de autocorrelación espacial observado en los votos del PP sea el resultado del azar. Es decir, existe evidencia significativa para sugerir que hay una estructura espacial en la distribución de los votos del PP en las provincias, excluyendo Madrid y Barcelona. Esto podría indicar la presencia de patrones geográficos en el comportamiento de voto del PP en esas provincias.

Índice I de Moran global (k vecinos más próximos)¶

In [43]:
from libpysal.weights import KNN

w_knn = KNN.from_dataframe(provincias_sin_mb, k=5)
votos_pp = provincias_sin_mb['PP']

# Índice I global de Moran, con 5 vecinos
mi_pp = Moran(votos_pp, w_knn, permutations=9999)
print(f"I Global de Moran: {mi_pp.I}")
print(f"Valor p: {mi_pp.p_norm}")
I Global de Moran: 0.2539311170946295
Valor p: 0.000746308013532381
  • El índice global de Moran, que mide la autocorrelación espacial en los datos, es un valor positivo. Por tanto, sugiere una autocorrelación espacial positiva, lo que significa que las áreas geográficas similares tienden a agruparse.

  • El p valor es la probabilidad de obtener un índice de Moran igual o más extremo por azar. En este caso, 0.00074 < 0.05. Consideramos entonces que hay evidencia significativa para rechazar la hipótesis nula de aleatoriedad espacial.

Diagrama de dispersión de Moran¶

In [44]:
from esda.moran import Moran_Local
import libpysal
from libpysal.weights import Queen #otros pesos: Rook, KNN
w_queen_sin_mb = Queen.from_dataframe(provincias_sin_mb) 
w_queen_sin_mb.n

# Obtener los votos del PP
votos_pp = provincias_sin_mb['PP']

# Calcular Moran_Local
moran_loc_pp = Moran_Local(votos_pp, w_queen_sin_mb, permutations=9999)

# Visualizar los resultados
moran_loc_pp
Out[44]:
<esda.moran.Moran_Local at 0x7fd6ecf60050>
In [45]:
from splot.esda import moran_scatterplot

# Crear el Moran Scatterplot para los votos del PP
fig, ax = moran_scatterplot(moran_loc_pp, p=0.05)
ax.set_xlabel('Votos del PP')
ax.set_ylabel('Spatial Lag of "Votos del PP"')
plt.title('Moran Scatterplot - Votos del PP')
plt.show()
No description has been provided for this image

Ajustamos el Moran Scatterplot para representar los votos del PP y el retardo espacial (representa la media ponderada de los votos del PP en los vecinos espaciales de cada provincia). En este análisis identificamos patrones espaciales de agrupación, es decir, si las provincias con altos (o bajos) votos del PP están rodeadas principalmente por provincias con patrones similares. Y como podemos observar, agrupaciones de valores altos rodeados de valores altos (rojo) y valores bajos rodeados de valores bajos (azul). En azul claro y naranja, posibles anomalías que no presentan agrupación ninguna.

Moran Global¶

In [46]:
from pysal.explore import esda
from pysal.lib import weights

# Calculamo pesos espaciales (utilizando, por ejemplo, vecinos más cercanos KNN)
w = weights.KNN.from_dataframe(elecciones_provi_shp, k=5)

# Calculamos el índice de Moran global para los votos del PP
moran_pp = esda.Moran(elecciones_provi_shp['PP'], w)

# resultados
print("Índice de Moran Global para Votos del PP:", moran_pp.I)
print("Valor p para Votos del PP:", moran_pp.p_sim)
Índice de Moran Global para Votos del PP: -0.062486860091909846
Valor p para Votos del PP: 0.239

El índice de Moran Global para los votos del PP es -0.0625 y el p valor es 0.248. Este resultado indica una baja correlación espacial global para los votos del PP. La falta de significancia estadística (p valor alto) sugiere que no hay evidencia suficiente para rechazar la hipótesis nula de ausencia de autocorrelación espacial global en los votos del PP.

El índice de Moran Global varía entre -1 y 1, donde los valores cercanos a 1 indican una fuerte autocorrelación positiva y cercanos a -1 una fuerte autocorrelación negativa, y valores cercanos a 0 indican falta de autocorrelación. En este caso, el índice es cercano a 0 y el p valor alto sugieren que los votos del PP no muestran una autocorrelación espacial significativa en el conjunto de datos.

In [47]:
from splot.esda import lisa_cluster

# Calcular el índice de Moran local
moran_loc_pp = esda.Moran_Local(elecciones_provi_shp['PP'], w)

# Gráficos de Autocorrelación espacial local (LISA)
p_thresS = [0.2, 0.1, 0.05, 0.01]

# Cálculo de los valores LISA para cada valor p con un bucle "for"
f, axs = plt.subplots(1, len(p_thresS), figsize=(21, 5))

for i, p_thres in enumerate(p_thresS):
    lisa_cluster(moran_loc_pp, elecciones_provi_shp, p_thres, figsize=(7, 7), ax=axs[i])
    axs[i].set_title('Votos del PP | LISA clusters | p-value = %.3f' % p_thres)

plt.show()
No description has been provided for this image

Los valores en la lista p_thresS representan diferentes niveles de significancia estadística que se utilizan para identificar patrones espaciales locales. Estos niveles (como 0.2, 0.1, 0.05, 0.01) se comparan con p valores calculados en un análisis espacial. Cuanto menor sea el p valor, mayor es la evidencia estadística de un patrón espacial significativo. El código proporcionado utiliza estos valores para visualizar patrones locales en datos geográficos a diferentes niveles de confianza estadística.

En el contexto de un análisis LISA (Local Indicators of Spatial Association), se utilizan diferentes códigos para describir los patrones espaciales locales. Estos códigos representan las combinaciones de valores altos (H, High) y bajos (L, Low) en una variable en un área específica y sus áreas vecinas. Los códigos comunes son:

  • HH (High-High): Representa áreas con valores altos rodeadas principalmente por áreas con valores altos. Indica la presencia de clusters locales de altos valores.

  • HL (High-Low): Indica áreas con valores altos rodeadas por áreas con valores bajos. Esto sugiere un patrón de valores altos en áreas aisladas.

  • LH (Low-High): Representa áreas con valores bajos rodeadas principalmente por áreas con valores altos. Indica la presencia de clusters locales de bajos valores.

  • LL (Low-Low): Indica áreas con valores bajos rodeadas principalmente por áreas con valores bajos. Refleja la presencia de clusters locales de bajos valores.

  • NS (Not Significant): Los valores no muestran patrones espaciales locales significativos.

Índice de Moran bivariante (PP y PSOE)¶

Evaluaremos la autocorrelación espacial entre dos variables (PP y PSOE).

In [48]:
#con madrid y barclona en los datos

from esda.moran import Moran_BV, Moran_Local_BV
from splot.esda import plot_moran_bv_simulation, plot_moran_bv

PP = elecciones_provi_shp['PP'].values
PSOE = elecciones_provi_shp['PSOE'].values

w_queen2 = Queen.from_dataframe(elecciones_provi_shp)

moran = Moran(PP,w_queen2)
moran_bv = Moran_BV(PP, PSOE, w_queen2)
moran_loc = Moran_Local(PP, w_queen2)
moran_loc_bv = Moran_Local_BV(PP, PSOE, w_queen2)
In [49]:
fig, axs = plt.subplots(2, 2, figsize=(15,10),
                        subplot_kw={'aspect': 'equal'})

moran_scatterplot(moran, ax=axs[0,0])
moran_scatterplot(moran_loc, p=0.05, ax=axs[1,0])
moran_scatterplot(moran_bv, ax=axs[0,1])
moran_scatterplot(moran_loc_bv, p=0.05, ax=axs[1,1])
plt.show()
No description has been provided for this image

Los resultados del Índice de Moran (-0.04) y del Índice Bivariado de Moran (-0.08) indican patrones de autocorrelación espacial negativa, lo que significa que las provincias con altos votos del PP tienden a estar rodeadas por provincias con bajos votos del PSOE y viceversa. Estos valores sugieren una tendencia a la dispersión espacial inversa entre los votos del PP y del PSOE.

El Índice de Moran cuantifica la autocorrelación espacial global, mientras que el Índice Bivariado de Moran examina la relación espacial entre dos variables. Ambos índices varían entre -1 y 1. Valores cercanos a 1 indican autocorrelación espacial positiva, valores cercanos a -1 indican autocorrelación espacial negativa, y valores cercanos a 0 indican distribución espacial aleatoria.

La existencia de valores atípicos (outliers) desvían los resultados. Procedemos a eliminarlos, como hicimos anteriormente.

Índice de Moran bivariante (PP y PSOE) - Sin outliers (Madrid y Barcelona)¶

eliminamos los valores atipicos

In [50]:
PP = provincias_sin_mb['PP'].values
PSOE = provincias_sin_mb['PSOE'].values

w_queen2 = Queen.from_dataframe(provincias_sin_mb)

moran = Moran(PP,w_queen2)
moran_bv = Moran_BV(PP, PSOE, w_queen2)
moran_loc = Moran_Local(PP, w_queen2)
moran_loc_bv = Moran_Local_BV(PP, PSOE, w_queen2)
In [51]:
fig, axs = plt.subplots(2, 2, figsize=(15,10),
                        subplot_kw={'aspect': 'equal'})

moran_scatterplot(moran, ax=axs[0,0])
moran_scatterplot(moran_loc, p=0.05, ax=axs[1,0])
moran_scatterplot(moran_bv, ax=axs[0,1])
moran_scatterplot(moran_loc_bv, p=0.05, ax=axs[1,1])
plt.show()
No description has been provided for this image
  1. Índice de Moran (Global) para Votos del PP:

    • Valor: 0.31
    • El valor es positivo. Esto indica una autocorrelación espacial positiva, por tanto, las áreas con altos votos del PP tienden a estar rodeadas por áreas con altos votos del PP, y lo mismo para las áreas con bajos votos. Existe la presencia de agrupaciones espaciales de votos del PP.
  2. Índice Bivariado de Moran (Global) entre Votos del PP y PSOE:

    • Valor: 0.25
    • El valor es positivo. Esto indica una relación espacial positiva entre los votos del PP y PSOE. Esto significa que las áreas con altos votos del PP tienden a estar rodeadas por áreas con altos votos del PSOE y viceversa.

Estos resultados indican patrones de agrupación espacial tanto para los votos del PP como para la relación espacial entre los votos del PP y PSOE. Los valores positivos sugieren que las áreas con votos similares están agrupadas espacialmente.

SELECCIONA UNA COMUNIDAD AUTÓNOMA¶

De la lista que aparecerá al ejecutar el chunk, escribe una comunidad autónoma y se te devolverá un mapa con los resultados elecctorales a nivel municipal de la Comunidad Autónoma elegida. Posteriormente, representará lo mismo que se calculó arriba. El índice de Moran Global y Bivariado en función del Partido Popular (PP) y el Partido Solialista (PSOE) para examinar la relación espacial entre estos dos partidos en dicha Comunidad Autónoma.

In [62]:
#Print de las comunidades autonomas
print("Nombres de todas las Comunidades Autónomas:")
print(elecciones['Nombre de Comunidad'].unique())

# Pedir al usuario que elija una comunidad autónoma
comunidad_autonoma_elegida = input("Ingrese el nombre de la comunidad autónoma para ver los resultados electorales: ")

# Filtramos municipios de la comunidad autónoma elegida
municipios_comunidad_autonoma = elecciones[elecciones['Nombre de Comunidad'] == comunidad_autonoma_elegida]

# Verificamos si la comunidad autónoma elegida existe en los datos
if municipios_comunidad_autonoma.empty:
    print(f"No hay datos disponibles para la comunidad autónoma de {comunidad_autonoma_elegida}.")
else:
    # Creamos el mapa en la comunidad autónoma elegida
    comunidad_autonoma_mapa = municipios_comunidad_autonoma.plot(figsize=(12, 12), linewidth=0.3, edgecolor='0.2',
                                                                 color=[colores_partidos.get(partido, 'lightgray') for partido in municipios_comunidad_autonoma['Partido_Mas_Votado']])

    # leyenda
    patches = [plt.Line2D([0], [0], marker='o', color='w', markerfacecolor=colores_partidos.get(partido, 'lightgray'),
                          markersize=10, label=partido) for partido in colores_partidos.keys()]
    comunidad_autonoma_mapa.legend(handles=patches, title='Partido Más Votado', loc='upper left', bbox_to_anchor=(1, 1))

    # plot
    plt.title(f'Partidos más votados en {comunidad_autonoma_elegida}')
    plt.show()


        # Filtrar valores atípicos altos (por ejemplo, municipios con votos del PP y PSOE mayores que un umbral)
    umbral_valor_atipico = 50000  
    
    municipios_comunidad_autonoma_filtrados = municipios_comunidad_autonoma[municipios_comunidad_autonoma['PP'] <= umbral_valor_atipico]
    municipios_comunidad_autonoma_filtrados = municipios_comunidad_autonoma[municipios_comunidad_autonoma['PSOE'] <= umbral_valor_atipico]

    
    # MORAN
    PP_filtrados = municipios_comunidad_autonoma_filtrados['PP'].values
    PSOE_filtrados = municipios_comunidad_autonoma_filtrados['PSOE'].values
    
    w_queen2_ = Queen.from_dataframe(municipios_comunidad_autonoma_filtrados)
    
    moran = Moran(PP_filtrados, w_queen2_)
    moran_bv = Moran_BV(PP_filtrados, PSOE_filtrados, w_queen2_)
    moran_loc = Moran_Local(PP_filtrados, w_queen2_)
    moran_loc_bv = Moran_Local_BV(PP_filtrados, PSOE_filtrados, w_queen2_)
    
    fig, axs = plt.subplots(2, 2, figsize=(15, 10), subplot_kw={'aspect': 'equal'})
    
    moran_scatterplot(moran, ax=axs[0, 0])
    moran_scatterplot(moran_loc, p=0.05, ax=axs[1, 0])
    moran_scatterplot(moran_bv, ax=axs[0, 1])
    moran_scatterplot(moran_loc_bv, p=0.05, ax=axs[1, 1])
    plt.show()
Nombres de todas las Comunidades Autónomas:
['Andalucía' 'Aragón' 'Principado de Asturias' 'Cantabria'
 'Castilla - La Mancha' 'Castilla y León' 'Cataluña' 'Extremadura'
 'Galicia' 'Comunidad de Madrid' 'Comunidad Foral de Navarra' 'País Vasco'
 'Región de Murcia' 'La Rioja' 'Comunitat Valenciana']
No description has been provided for this image
/home/bdg/anaconda3/envs/crime/lib/python3.11/site-packages/libpysal/weights/weights.py:224: UserWarning: The weights matrix is not fully connected: 
 There are 2 disconnected components.
 There is 1 island with id: 397.
  warnings.warn(message)
('WARNING: ', 397, ' is an island (no neighbors)')
No description has been provided for this image

Declaración de Autor (CRediT statement):¶

Fossoul_Valerio_Marco: Metodología, Software, Análisis, Visualización, Investigación, Validación, Validación.

Díaz_Nieto_Marcos: Conceptualización, Metodología, Búsqueda y preparación de los datos, Análisis, visualización, Redacción.